package com.sky.wsp.license.client;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author wangshaopeng@talkweb.com.cn
 * @desccription license校验，只用到JDK自带的API，不依赖任何三方jar
 * @date 2024/6/26
 */
public class License {
    public static void main(String[] args) {
        String certificateStr = "MIICqzCCAZOgAwIBAgIBATANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5NeSBDZXJ0aWZpY2F0ZTAeFw0yNDA2MjUwODA3MzJaFw0yNTA2MjUwODA3MzJaMBkxFzAVBgNVBAMMDk15IENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAihtHw7DRf2pDZ98LIX/OfOv3/OMTf4CBYS4d6UruN99D869+365ITGmqQCu0ufGM3fiyr6YNo2Fg41Kc2mEVyi0AsitDHVRRp89nWBqQWatxCtIZ7xKvl+HfFLKQGzIN8ER8bHzqwiLt38RcWnEFvMtbN5SvywkNM/m+8UWhEPGIHHjjZXGegaTyVxW7aAeHWTe+eWm/eIWEnH1SIrAPL/OwI+0DxAIfA2/M629KEDJuZMGWOn0XoXmry4U2B7kypA+jsilzsPLPp18ONHYy01NpjCh0HnDMMtyiXry5dYHygIoYbhTj4YIdr7RW/3W1Y/pxGdH0otR7S1lVksVLTwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCIvj3sFFrJLnpzRYA2Jb3RxHUANxc3BMdLMJOqzvDSg9Yf6rdIlV6qEEz0qLlPsdsOBu+YLMkWd2oXs5JwHh+EfsQHq09VRrLwOoxUtKj2RRvCqRuv+B76d6m17uNLuuUzgQYSucPz0r16cWKukx7FLiXJC7yFKPs6xn1DpMW7/qJ5Fq5ZOsvNyd/wWzfULBHqAhhdp8hwZo6zGLRbCVveNWgoOiUEPtdkE/uqIyzN1rfc1fieg0ktx0kwz1JfWc13YpAH/83TdXrQCb8DPeROuMznV3j7JO1R4WIOL4vxEZigEACLs9dGMYHD3POD+fGXTQoamsFuq4+8YXjoiJmO";
        String licensePath = "license.lic";
        Map<String, Object> b = parse(certificateStr, licensePath);
        System.out.println(b);
    }

    /**
     * 解析证书并校验有效期，mac\ip\k8s-id等，只解析不校验，返回后按需自行校验
     */
    public static Map<String, Object> parse(String certificateStr, String licensePath) {
        File licenseFile = new File(licensePath);
        if (!licenseFile.exists()) {
            licenseFile = new File("license.lic");
            if (!licenseFile.exists()) {
                throw new RuntimeException("请将license.lic放在" + licenseFile.getParent() + "目录下");
            }
        }
        BufferedReader br = null;
        String license;
        try {
            br = new BufferedReader(new FileReader(licensePath));
            license = br.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (null != br) try {
                br.close();
            } catch (IOException e) {
            }
        }
        String[] split = license.split("\\.");
        String sign = split[0];
        String xml = split[1];

        Base64.Decoder decoder = Base64.getDecoder();
        byte[] decodeCert = decoder.decode(certificateStr);
        byte[] decodeSign = decoder.decode(sign);
        String decodeXml = new String(decoder.decode(xml), StandardCharsets.UTF_8);

        X509Certificate certificate;
        boolean valid = false;
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(decodeCert));

            Signature signature = Signature.getInstance(certificate.getSigAlgName());
            /** 这里用到的是证书，实际上用到的也是证书里面的公钥 */
            signature.initVerify(certificate);
            signature.update(decodeXml.getBytes(StandardCharsets.UTF_8));
            valid = signature.verify(decodeSign);
        } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
//            e.printStackTrace();
        }
        if (!valid) {
            throw new RuntimeException("license.lic invalid!");
        }
        Map<String, Object> licenseConfig = xmlToMap(decodeXml);
        if (null == licenseConfig) {
            throw new RuntimeException("license.lic invalid!");
        }
        String expiryTimeStr = (String) licenseConfig.get("expiryTime");
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        format.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        Date expiryTime = null;
        try {
            expiryTime = format.parse(expiryTimeStr);
        } catch (ParseException e) {
            throw new RuntimeException("license.lic invalid!");
        }
        if (System.currentTimeMillis() > expiryTime.getTime()) {
            throw new RuntimeException("license.lic expired!");
        } else {
            System.out.println(String.format("证书信息：\n产品:%s\n版本号:%s\n用途:%s\n主题:%s\n颁发时间:%s\n有效期:%s\n",
                    licenseConfig.get("product"),
                    licenseConfig.get("version"),
                    licenseConfig.get("description"),
                    licenseConfig.get("subject"),
                    licenseConfig.get("issuedTime"),
                    expiryTimeStr
            ));
        }
        return licenseConfig;
    }

    public static Map<String, Object> xmlToMap(String xml) {
        final Map<String, Object> licenseConfig = new HashMap<>();
        try {
            //创建一个新的SAXBuilder
            SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
            XMLReader xmlReader = saxParser.getXMLReader();
            xmlReader.setContentHandler(new DefaultHandler() {
                private String currentElement;
                private String currentValue;

                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes) {
                    currentElement = qName;
//                    System.out.println("一个元素开始...." + qName);
                    if ("licenseConfig".equals(qName)) {
                        licenseConfig.put("checkModels", new ArrayList<Map<String, Object>>());
                    } else if ("checkModels".equals(qName)) {
                        ArrayList<Map<String, Object>> checkModels = (ArrayList<Map<String, Object>>) licenseConfig.get("checkModels");
                        checkModels.add(new HashMap<>());
                    } else if (qName.equals("domainName")) {
                        licenseConfig.put("domainName", new HashMap<String, Object>());
                    }
                }

                @Override
                public void characters(char[] ch, int start, int length) {
                    currentValue = new String(ch, start, length);
//                    System.out.println("一个元素读取...." + currentElement + "==" + currentValue);
                }

                @Override
                public void endElement(String uri, String localName, String qName) {
                    if (!currentElement.equals(qName)) {
//                        System.out.println("父元素结束...." + currentElement + "-->" + qName);
                        return;
                    }
                    if ("licenseConfig".equals(qName) || "checkModels".equals(qName) || qName.equals("domainName")) {
                        return;
                    } else if ("cpuSerial".equals(qName) || "ip".equals(qName) || "mac".equals(qName) || "mainBoardSerial".equals(qName)) {
                        ArrayList<Map<String, Object>> checkModels = (ArrayList<Map<String, Object>>) licenseConfig.get("checkModels");
                        checkModels.get(checkModels.size() - 1).put(qName, currentValue);
                    } else if ("commonName".equals(qName) || "country".equals(qName) || "localityName".equals(qName) || "organizationName".equals(qName) || "organizationUnit".equals(qName) || "stateName".equals(qName)) {
                        Map<String, Object> domainName = (Map<String, Object>) licenseConfig.get("domainName");
                        domainName.put(qName, currentValue);
                    } else {
                        licenseConfig.put(qName, currentValue);
                    }
                }
            });
            xmlReader.parse(new InputSource(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))));
            return licenseConfig;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
